/*******************************************************************************
 * lz77.c
 *
 * Copyright (c) 2009 The Lemon Man
 * Copyright (c) 2009 Nicksasa
 * Copyright (c) 2009 WiiPower
 *
 * Distributed under the terms of the GNU General Public License (v2)
 * See http://www.gnu.org/licenses/gpl-2.0.txt for more info.
 *
 * Description:
 * -----------
 *
 ******************************************************************************/

#include <gccore.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wiiuse/wpad.h>

#include "lz77.h"
#include "debug.h"

u32 getLowestMem2Address() { return (u32) SYS_GetArena2Lo(); }

u32 packBytes(int a, int b, int c, int d)
{
    return (d << 24) | (c << 16) | (b << 8) | (a);
}

s32 __decompressLZ77_11(u8 *in, u32 inputLen, u8 **output, u32 *outputLen, u32 max_size)
{
    int x, y;

    u8 *out = NULL;

    u32 compressedPos = 0x4;
    u32 decompressedPos = 0x0;
    u32 decompressedSize = 0;

    decompressedSize = packBytes(in[0], in[1], in[2], in[3]) >> 8;

    if (!decompressedSize)
    {
        decompressedSize = packBytes(in[4], in[5], in[6], in[7]);
        compressedPos += 0x4;
    }

    //dbg_printf("Decompressed size : %i\n", decompressedSize);

	if (max_size == 0)
	{
		max_size = decompressedSize;
	} else
	{
		if (max_size > decompressedSize)
		{
			return -1;
		}
	}

    out = malloc(max_size);
	if (out == NULL)
	{
		dbg_printf("Out of memory\n");
		return -1;
	}

    while (compressedPos < inputLen && decompressedPos < max_size)
    {
        u8 byteFlag = in[compressedPos];
        compressedPos++;

        for (x = 7; x >= 0; x--)
        {
            if ((byteFlag & (1 << x)) > 0)
            {
                u8 first = in[compressedPos];
                u8 second = in[compressedPos + 1];

                u32 pos, copyLen;

                if (first < 0x20)
                {
                    u8 third = in[compressedPos + 2];

                    if (first >= 0x10)
                    {
                    u32 fourth = in[compressedPos + 3];

                    pos = (u32)(((third & 0xF) << 8) | fourth) + 1;
                    copyLen = (u32)((second << 4) | ((first & 0xF) << 12) | (third >> 4)) + 273;

                    compressedPos += 4;
                    } else
					{
						pos = (u32)(((second & 0xF) << 8) | third) + 1;
						copyLen = (u32)(((first & 0xF) << 4) | (second >> 4)) + 17;

						compressedPos += 3;
					}
				} else
				{
					pos = (u32)(((first & 0xF) << 8) | second) + 1;
					copyLen = (u32)(first >> 4) + 1;

					compressedPos += 2;
				}

				for (y = 0; y < copyLen; y++)
				{
					if (decompressedPos + y < max_size)
					{
						out[decompressedPos + y] = out[decompressedPos - pos + y];
					}
				}

				decompressedPos += copyLen;
			} else
			{
				if (decompressedPos < max_size)
				{
					out[decompressedPos] = in[compressedPos];
				}

				decompressedPos++;
				compressedPos++;
			}

			if (compressedPos >= inputLen || decompressedPos >= decompressedSize)
				break;
		}
	}

	if (compressedPos > inputLen || decompressedPos > decompressedSize)
	{
		return -1;
	}

	*output = out;
	*outputLen = decompressedSize;
	return 0;
}

s32 __decompressLZ77_10(u8 *in, u32 inputLen, u8 **output, u32 *outputLen, u32 max_size)
{
	int x, y;

	u8 *out = NULL;

	u32 compressedPos = 0;
	u32 decompressedSize = 0x4;
	u32 decompressedPos = 0;

	decompressedSize = packBytes(in[0], in[1], in[2], in[3]) >> 8;

	//int compressionType = (packBytes(in[0], in[1], in[2], in[3]) >> 4) & 0xF;

	//dbg_printf("Decompressed size : %i\n", decompressedSize);

	if (max_size == 0)
	{
		max_size = decompressedSize;
	} else
	{
		if (max_size > decompressedSize)
		{
			return -1;
		}
	}

	out = malloc(max_size);
	if (out == NULL)
	{
		dbg_printf("Out of memory\n");
		return -1;
	}

	compressedPos += 0x4;

	while (compressedPos < inputLen && decompressedPos < max_size)
	{
		u8 flag = *(u8*)(in + compressedPos);
		compressedPos += 1;

		for (x = 0; x < 8; x++)
		{
			if (flag & 0x80)
			{
				u8 first = in[compressedPos];
				u8 second = in[compressedPos + 1];

				u16 pos = (u16)((((first << 8) + second) & 0xFFF) + 1);
				u8 copyLen = (u8)(3 + ((first >> 4) & 0xF));

				for (y = 0; y < copyLen; y++)
				{
					if (decompressedPos + y < max_size)
					{
						out[decompressedPos + y] = out[decompressedPos - pos + (y % pos)];
					}
				}

				compressedPos += 2;
				decompressedPos += copyLen;
			} else
			{
				if (decompressedPos < max_size)
				{
					out[decompressedPos] = in[compressedPos];
				}
				compressedPos += 1;
				decompressedPos += 1;
			}

			flag <<= 1;

			if (compressedPos >= inputLen || decompressedPos >= decompressedSize)
				break;
		}
	}

	if (compressedPos > inputLen || decompressedPos > decompressedSize)
	{
		return -1;
	}

	*output = out;
	*outputLen = decompressedSize;
	return 0;
}

int isLZ77compressed(u8 *buffer)
{
	if ((buffer[0] == LZ77_0x10_FLAG) || (buffer[0] == LZ77_0x11_FLAG))
	{
		return 1;
	}

	return 0;
}

int decompressLZ77content(u8 *buffer, u32 length, u8 **output, u32 *outputLen, u32 max_size)
{
    int ret;
	switch (buffer[0])
    {
        case LZ77_0x10_FLAG:
            //dbg_printf("LZ77 variant 0x10 compressed content...unpacking may take a while...\n");
            ret = __decompressLZ77_10(buffer, length, output, outputLen, max_size);
			break;
        case LZ77_0x11_FLAG:
            //dbg_printf("LZ77 variant 0x11 compressed content...unpacking may take a while...\n");
            ret = __decompressLZ77_11(buffer, length, output, outputLen, max_size);
			break;
        default:
            dbg_printf("Not compressed ...\n");
			ret = -1;
			break;
    }
	return ret;
}